/* 
 * Copyright 2010 Ziya Guven Koylu, Ozge Koylu
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.koylu.caffein.model.clazz;

import java.sql.Connection;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javassist.util.proxy.ProxyObject;

import org.apache.commons.beanutils.PropertyUtils;

import com.koylu.caffein.Cascade;
import com.koylu.caffein.exception.ClazzConfigException;
import com.koylu.caffein.model.caffein.CaffeinConfig;
import com.koylu.caffein.model.clazz.id.CompositeId;
import com.koylu.caffein.model.clazz.validation.ValidateFor;
import com.koylu.caffein.model.clazz.validation.Validation;
import com.koylu.caffein.model.handler.ManyToOneContainerHandler;

public class ManyToOne extends Relation {

	private String name;
	private String clazz;
	private Node parent;
	private Class<?> proxyClass;
	private Collection<ForeignKey> children;
	private List<Validation> validations;
	private String path;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getClazz() {
		return clazz;
	}

	public void setClazz(String clazz) {
		this.clazz = clazz;
	}
	
	public Clazz getForeignClazz(CaffeinConfig caffeinConfig) throws Exception {
		Clazz clazzz = caffeinConfig.getClazz(clazz);
		if(clazzz == null){
			throw new ClazzConfigException("clazz not found:"+clazz, this);
		}
		return clazzz;
	}

	public Node getParent() {
		return parent;
	}

	public void setParent(Node parent) {
		this.parent = parent;
	}

	public void addChild(Node child) {
		if (children == null) {
			children = new ArrayList<ForeignKey>();
		}
		child.setParent(this);
		children.add((ForeignKey) child);
	}

	public Collection<ForeignKey> getChildren() {
		return children;
	}

	public String getCascade() {
		return Cascade.NOACTION.name();
	}

	Boolean isSimple;
	public Object toObject(CaffeinConfig caffeinConfig, Object object, ResultSet resultSet) throws Exception {
		if (isSimple == null) {
			isSimple = !(getForeignClazz(caffeinConfig).getId() instanceof CompositeId);
		}
		if (isSimple) {
			return toSimpleObject(caffeinConfig, resultSet);
		}
		return toCompositeObject(caffeinConfig, resultSet);
	}

	ForeignKey foreignKey = null;
	public Object toSimpleObject(CaffeinConfig caffeinConfig, ResultSet resultSet) throws Exception {
		if (foreignKey == null) {
			foreignKey = getChildren().iterator().next();
		}
		Object object = getProxyClass(caffeinConfig).newInstance();
		Object id = foreignKey.getConverter(caffeinConfig).columnToField(resultSet, foreignKey.getColumnAlias());
		if (!foreignKey.getConverter(caffeinConfig).isNull(id)) {
			((ProxyObject) object).setHandler(new ManyToOneContainerHandler(caffeinConfig, getForeignClazz(caffeinConfig), id));
		}else{
			((ProxyObject) object).setHandler(new ManyToOneContainerHandler(caffeinConfig, getForeignClazz(caffeinConfig), null));	
		}
		return object;
	}

	public Object toCompositeObject(CaffeinConfig caffeinConfig, ResultSet resultSet) throws Exception {
		boolean createObject = true;
		Object id = ((CompositeId)getForeignClazz(caffeinConfig).getId()).getRealClass(caffeinConfig).newInstance();
		for (ForeignKey foreignKey : getChildren()) {
			Object value = foreignKey.getConverter(caffeinConfig).columnToField(resultSet, foreignKey.getColumnAlias());
			if (foreignKey.getConverter(caffeinConfig).isNull(value)) {
				createObject = false;
				break;
			} else {
				PropertyUtils.setNestedProperty(id, foreignKey.getRefNode(caffeinConfig).getPathForId(caffeinConfig), value);
			}
		}
		Object object = getProxyClass(caffeinConfig).newInstance();
		if (createObject) {
			((ProxyObject) object).setHandler(new ManyToOneContainerHandler(caffeinConfig, getForeignClazz(caffeinConfig), id));
		}else{
			((ProxyObject) object).setHandler(new ManyToOneContainerHandler(caffeinConfig, getForeignClazz(caffeinConfig), null));
		}
		return object;
	}
	
	public Object toEmptyObject(CaffeinConfig caffeinConfig, Clazz clazz) throws Exception {
		if(getForeignClazz(caffeinConfig).getName().equals(clazz.getName())){
			return clazz.getRealClass(caffeinConfig).newInstance();
		}
		return getForeignClazz(caffeinConfig).toEmptyObject(caffeinConfig);
	}
	
	public Class<?> getProxyClass(CaffeinConfig caffeinConfig) throws Exception {
		if(proxyClass == null){
			proxyClass = getForeignClazz(caffeinConfig).getProxyClass(caffeinConfig);
		}
		return proxyClass;
	}

	public Class<?> toVirtualClass(CaffeinConfig caffeinConfig) throws Exception {
		return Object.class;
	}

	public Object copy(CaffeinConfig caffeinConfig, Object object) throws Exception {
		Object id = PropertyUtils.getProperty(object, getForeignClazz(caffeinConfig).getId().getName());
		Object newObject = getProxyClass(caffeinConfig).newInstance();
		((ProxyObject) newObject).setHandler(new ManyToOneContainerHandler(caffeinConfig, getForeignClazz(caffeinConfig), id));
		return newObject;
	}
	
	public List<Validation> getValidations() {
		return validations;
	}

	public void setValidations(List<Validation> validations) {
		this.validations = validations;
	}
	
	public void validate(CaffeinConfig caffeinConfig, Clazz clazz, Object object, ValidateFor validateFor) throws Exception {
		if (validations != null) {
			for (Validation validation : validations) {
				validation.validate(caffeinConfig, clazz, this, object, validateFor);
			}
		}
	}
	
	public void save(CaffeinConfig caffeinConfig, Connection connection, Object object) throws Exception {}
	
	public void update(CaffeinConfig caffeinConfig, Connection connection, Object object) throws Exception {}
	
	public void delete(CaffeinConfig caffeinConfig, Clazz clazz, Connection connection, Object object) throws Exception {}
	
	public String getPath(CaffeinConfig caffeinConfig) throws Exception {
		if (path == null) {
			path = getName();
			Node clazz = getParent();
			while(clazz.getParent() != null) {//!(clazz instanceof Clazz)
				path = ((Field) clazz).getName() + "." + path;
				clazz = clazz.getParent();
			}
		}
		return path;
	}

	public String toString() {
		return getClass().getName()+"["+name+"]";
	}
}